{"componentChunkName":"component---src-templates-post-js","path":"/simply-learn-full-stack-8","result":{"data":{"site":{"siteMetadata":{"title":"neohed","description":"Blog posts on web development and related areas","author":{"name":"neohed"},"keywords":["Web Development","JavaScript"]}},"mdx":{"frontmatter":{"title":"simply learn-full-stack-8","description":"Full-stack tutorial. Creating CRUD operations using Prisma","date":"November 21, 2022","author":null,"banner":null,"slug":"simply-learn-full-stack-8","keywords":null},"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsx mdx */\nvar _frontmatter = {\n  \"slug\": \"simply-learn-full-stack-8\",\n  \"date\": \"2022-11-21T11:25:39\",\n  \"title\": \"simply learn-full-stack-8\",\n  \"description\": \"Full-stack tutorial. Creating CRUD operations using Prisma\",\n  \"published\": true\n};\n\nvar makeShortcode = function makeShortcode(name) {\n  return function MDXDefaultShortcode(props) {\n    console.warn(\"Component \" + name + \" was not imported, exported, or provided by MDXProvider as global scope\");\n    return mdx(\"div\", props);\n  };\n};\n\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h1\", null, \"Simply Learn Full-Stack React & Node.js\"), mdx(\"p\", null, \"Let's wrap things up.\"), mdx(\"p\", null, \"In folder \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"node-server\"), \" edit \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"note.model.js\"), \" to:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-javascript\"\n  }), \"const { prisma } = require(\\\"./db\\\")\\n\\nasync function getNotes() {\\n  return prisma.note.findMany()\\n}\\n\\nasync function getNote(id) {\\n  return prisma.note.findUnique({ where: { id } })\\n}\\n\\nasync function createNote(\\n  note\\n) {\\n  return prisma.note.create({\\n    data: note\\n  })\\n}\\n\\nasync function updateNote(\\n  id, note\\n) {\\n  return prisma.note.update({\\n    data: note,\\n    where: {\\n      id\\n    }\\n  })\\n}\\n\\nasync function deleteNote(\\n  id\\n) {\\n  return prisma.note.delete({\\n    where: {\\n      id\\n    }\\n  })\\n}\\n\\nmodule.exports = {\\n  getNotes,\\n  getNote,\\n  createNote,\\n  updateNote,\\n  deleteNote,\\n}\\n\")), mdx(\"p\", null, \"In folder \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"node-server\"), \" edit \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"note.controller.js\"), \" to:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-javascript\"\n  }), \"const authorRepo = require('../models/author.model');\\nconst noteRepo = require('../models/note.model');\\n\\nasync function getNotes(req, res) {\\n  const notes = await noteRepo.getNotes();\\n\\n  res.json({\\n    notes\\n  });\\n}\\n\\nasync function getNote(req, res) {\\n  const {id} = req.params;\\n  const note = await noteRepo.getNote(id);\\n  const { authorId, ...noteRest } = note;\\n  const { username } = await authorRepo.getAuthor(authorId);\\n\\n  res.json({ note: {\\n      ...noteRest,\\n      author: username\\n    }\\n  });\\n}\\n\\nasync function retrieveOrCreateAuthor(username) {\\n  let author = await authorRepo.getAuthorByName(username);\\n  if (author === null) {\\n    author = await authorRepo.createAuthor({\\n      username\\n    })\\n  }\\n\\n  return author\\n}\\n\\nasync function postNote(req, res) {\\n  const {body} = req;\\n  const {title, content, author, lang, isLive, category} = body;\\n\\n  try {\\n    const noteAuthor = await retrieveOrCreateAuthor(author);\\n\\n    const note = await noteRepo.createNote({\\n      title,\\n      content,\\n      lang,\\n      isLive,\\n      category,\\n      authorId: noteAuthor.id\\n    })\\n\\n    res\\n      .status(200)\\n      .json({\\n        note\\n      })\\n  } catch (e) {\\n    console.error(e);\\n    res.status(500).json({error: \\\"Something went wrong\\\"})\\n  }\\n}\\n\\nasync function putNote(req, res) {\\n  const {body} = req;\\n  const {id, title, content, author, lang, isLive, category} = body;\\n\\n  try {\\n  const noteAuthor = await retrieveOrCreateAuthor(author);\\n  const note = await noteRepo.updateNote(id, {\\n    title,\\n    content,\\n    lang,\\n    isLive,\\n    category,\\n    authorId: noteAuthor.id\\n  })\\n\\n  res\\n    .status(200)\\n    .json({\\n      note\\n    })\\n  } catch (e) {\\n    console.error(e);\\n    res.status(500).json({error: \\\"Something went wrong\\\"})\\n  }\\n}\\n\\nasync function deleteNote(req, res) {\\n  const {body} = req;\\n  const {id} = body;\\n\\n  try {\\n    await noteRepo.deleteNote(id)\\n\\n    res\\n      .status(200).send()\\n  } catch (e) {\\n    console.error(e);\\n    res.status(500).json({error: \\\"Something went wrong\\\"})\\n  }\\n}\\n\\nmodule.exports = {\\n  getNotes,\\n  getNote,\\n  postNote,\\n  putNote,\\n  deleteNote,\\n}\\n\")), mdx(\"p\", null, \"In \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"node-server\"), \" edit \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"routes/index.js\"), \" to:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-javascript\"\n  }), \"const express = require('express');\\nconst noteRouter = express.Router();\\nconst noteController = require('../controllers/note.controller');\\n\\nnoteRouter.get('/', noteController.getNotes);\\nnoteRouter.get('/:id', noteController.getNote);\\nnoteRouter.post('/', noteController.postNote);\\nnoteRouter.put('/', noteController.putNote);\\nnoteRouter.delete('/', noteController.deleteNote);\\n\\nconst routes = app => {\\n  app.use('/note', noteRouter);\\n};\\n\\nmodule.exports = routes\\n\")), mdx(\"p\", null, \"Server side we now have all the operations we need for the basic CRUD operations.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Create, Read, Update, Delete\")), mdx(\"p\", null, \"Try running the client and server now. If you click the submit button on the form you'll notice two problems: first the form doesn't respond, you could click over and over and not know if anything's happened. Second, if you look at the server console you'll notice an error.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Argument isLive: Got invalid value 'true' on prisma.createOneNote. Provided String, expected Boolean.\")), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isLive\"), \" is a boolean but is being sent to Prisma as a string.\"), mdx(\"p\", null, \"In \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"node-server\"), \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"index.js\"), \" we are using:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-javascript\"\n  }), \"app.use(bodyParser.json());\\n\")), mdx(\"p\", null, \"This does indeed retrieve the correct types during parsing, so the problem must be in the client.  When we gather up the input control data in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Form.js\"), \" in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onSubmit\"), \" handler we are using \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"input.value\"), \" which always returns a string.\"), mdx(\"p\", null, \"Edit \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Form.js\"), \" to:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-javascript\"\n  }), \"import React, {useState} from 'react';\\nimport InputLabel from \\\"./InputLabel\\\";\\nimport {isEmptyString, isNullOrUndefined, titleFromName} from \\\"./strings\\\";\\nimport './form.css'\\n\\nconst Form = ({entity, onSubmitHandler, onDeleteHandler}) => {\\n  const [isSubmitting, setIsSubmitting] = useState(false);\\n\\n  return (\\n    <form onSubmit={e => {\\n      setIsSubmitting(true);\\n      const form = e.target;\\n      const newEntity = Object.values(form).reduce((obj, field) => {\\n        const {name} = field;\\n\\n        if (!isEmptyString(name)) {\\n          switch (typeof entity[name]) {\\n            case \\\"number\\\":\\n              obj[name] = field.valueAsNumber;\\n              break;\\n            case \\\"boolean\\\":\\n              obj[name] = field.value === 'true';\\n              break;\\n            default:\\n              obj[name] = field.value\\n          }\\n        }\\n\\n        return obj\\n    }, {})\\n      onSubmitHandler(newEntity);\\n\\n      e.stopPropagation();\\n      e.preventDefault()\\n    }}>\\n      <fieldset\\n        disabled={isSubmitting}\\n      >\\n      {\\n        Object.entries(entity).map(([entityKey, entityValue]) => {\\n          if (entityKey === \\\"id\\\") {\\n            return <input\\n              type=\\\"hidden\\\"\\n              name=\\\"id\\\"\\n              key=\\\"id\\\"\\n              value={entityValue}\\n            />\\n          } else {\\n            return <InputLabel\\n              id={entityKey}\\n              key={entityKey}\\n              label={titleFromName(entityKey)}\\n              type={\\n                typeof entityValue === \\\"boolean\\\"\\n                  ? \\\"checkbox\\\"\\n                  : \\\"text\\\"\\n              }\\n              value={entityValue}\\n            />\\n          }\\n        })\\n      }\\n      </fieldset>\\n      <button\\n        type=\\\"submit\\\"\\n        disabled={isSubmitting}\\n      >\\n        {\\n          isSubmitting ? 'Submitting' : 'Submit'\\n        }\\n      </button>\\n      {\\n        onDeleteHandler && !isNullOrUndefined(entity.id) && <button\\n          disabled={isSubmitting}\\n          onClick={() => {\\n            setIsSubmitting(true);\\n            onDeleteHandler(entity.id)\\n          }}\\n        >\\n          Delete\\n        </button>\\n      }\\n    </form>\\n  );\\n};\\n\\nexport default Form;\\n\")), mdx(\"p\", null, \"Changes:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We wrap our input controls with a fieldset tag, allowing us to disable all controls when the user clicks \\\"Submit\\\"\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We use a switch statement to parse the input value so it matches the type of the original entity we use to build the form.\")), mdx(\"p\", null, \"If you try saving a form again you'll notice the bug is fixed.\"), mdx(\"p\", null, \"Before we implement the rest of the CRUD operations a small refactor is needed. In \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"react-client\"), \", create .env.development\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-text\"\n  }), \"REACT_APP_URL_API=http://localhost:4011/\\n\")), mdx(\"p\", null, \"Create \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"useFetch.js\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-javascript\"\n  }), \"import {useState, useEffect} from \\\"react\\\";\\n\\nexport const getUrl = url => new URL(url, process.env.REACT_APP_URL_API).toString();\\n\\nfunction useFetch(url, skip) {\\n  const [data, setData] = useState({});\\n\\n  useEffect( () => {\\n    const abortController = new AbortController();\\n\\n    async function fetchData() {\\n      const fullUrl = getUrl(url);\\n      console.log('Fetching from: ' + fullUrl);\\n      try {\\n        const response = await fetch(fullUrl, {\\n          signal: abortController.signal,\\n        });\\n\\n        if (response.ok) {\\n          console.log('Response received from server and is ok!')\\n          const res = await response.json();\\n\\n          if (abortController.signal.aborted) {\\n            console.log('Abort detected, exiting!')\\n            return;\\n          }\\n\\n          setData(res)\\n        }\\n      } catch(e) {\\n        console.log(e)\\n      }\\n    }\\n\\n    !skip && fetchData()\\n\\n    return () => {\\n      console.log('Aborting GET request.')\\n      abortController.abort();\\n    }\\n  }, [url, setData, skip])\\n\\n  return data\\n}\\n\\nexport default useFetch\\n\")), mdx(\"p\", null, \"Currently our form can only add new notes, not edit. We need to do a few things:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"List all notes\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Edit a note\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Add a note\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Delete a note\")), mdx(\"p\", null, \"Refactor \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"AddEditNote.js\"), \" to:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-jsx\"\n  }), \"import React from 'react';\\nimport {useParams, useNavigate} from \\\"react-router-dom\\\";\\nimport RenderData from \\\"./RenderData\\\";\\nimport Form from './Form';\\nimport useFetch, {getUrl} from \\\"./useFetch\\\";\\nimport {isNullOrUndefined} from \\\"./strings\\\";\\n\\nconst AddEditNote = () => {\\n  const {noteId} = useParams();\\n  const {note = {\\n    title: '',\\n    content: '',\\n    lang: '',\\n    isLive: false,\\n    category: '',\\n    author: '',\\n  }} = useFetch('note/' + noteId, isNullOrUndefined(noteId));\\n  const navigate = useNavigate();\\n\\n  return (\\n    <div>\\n      <RenderData\\n        data={note}\\n      />\\n      <Form\\n        entity={note}\\n        onSubmitHandler={async newNote => {\\n          console.log({newNote})\\n          const response = await fetch(getUrl('note'), {\\n            method: isNullOrUndefined(newNote.id) ? 'POST' : 'PUT',\\n            body: JSON.stringify(newNote),\\n            headers: {\\n              'Content-Type': 'application/json'\\n            }\\n          });\\n\\n          if (response.ok) {\\n            await response.json()\\n            navigate('/note-list')\\n          }\\n        }}\\n        onDeleteHandler={async (id) => {\\n          if (!isNullOrUndefined(id)) {\\n            await fetch(getUrl('note'), {\\n              method: 'DELETE',\\n              body: JSON.stringify({id}),\\n              headers: {\\n                'Content-Type': 'application/json'\\n              }\\n            });\\n\\n            navigate('/note-list')\\n          }\\n        }}\\n      />\\n    </div>\\n  );\\n};\\n\\nexport default AddEditNote;\\n\")), mdx(\"p\", null, \"In \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"react-client\"), \" Create \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"TableList.js\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-jsx\"\n  }), \"import React from 'react';\\nimport {titleFromName} from './strings';\\nimport './table-list.css';\\n\\nconst TableList = ({\\n                     data,\\n                     title,\\n                     onClickHandler,\\n                     idField = 'id',\\n                     fieldFormatter = {},\\n                   }) => {\\n  if (!data || data.length === 0) {\\n    return null\\n  }\\n  const firstRow = data[0];\\n  const dataColumnNamesToRender = Object.getOwnPropertyNames(firstRow)\\n    .filter(propName => propName !== idField);\\n\\n  const headerRow = dataColumnNamesToRender.map((propName, i) => <th\\n    key={i}\\n  >\\n    {\\n      titleFromName(propName)\\n    }\\n  </th>);\\n\\n  return (\\n    <table>\\n      <caption>\\n        {\\n          title\\n        }\\n      </caption>\\n      <thead>\\n      <tr>\\n        {\\n          headerRow\\n        }\\n      </tr>\\n      </thead>\\n      <tbody>\\n      {\\n        data.map((dataRow, i) => <tr\\n          key={i}\\n          onClick={() => onClickHandler && onClickHandler(dataRow[idField])}\\n        >\\n          {\\n            dataColumnNamesToRender.map((dataColumnName, i) => <td\\n              key={i}\\n            >\\n              {\\n                (fieldFormatter[dataColumnName] ?? (v => v))(dataRow[dataColumnName], dataRow)\\n              }\\n            </td>)\\n          }\\n        </tr>)\\n      }\\n      </tbody>\\n    </table>\\n  );\\n};\\n\\nexport default TableList;\\n\")), mdx(\"p\", null, \"In \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"react-client\"), \" Create \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"table-list.css\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-css\"\n  }), \"table {\\n    margin: 12px;\\n    border-collapse: collapse;\\n}\\n\\nth {\\n    color: white;\\n    padding: 8px;\\n    background-color: #444;\\n}\\n\\ntd {\\n    border-bottom: 1px solid #ddd;\\n    padding: 12px;\\n}\\n\\ntd a,\\ntd a:visited {\\n    color: black;\\n}\\n\\ntd:not(:last-child) {\\n    border-left:1px solid #ccc;\\n    border-right: 1px solid #ccc;\\n}\\n\\ntr:nth-child(even) {\\n    background-color: #f1f1f1;\\n}\\n\")), mdx(\"p\", null, \"Just like our generic Form component, this is a generic data list component.\"), mdx(\"p\", null, \"In \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"react-client\"), \" Create \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"NoteList.js\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-jsx\"\n  }), \"import React from 'react';\\nimport TableList from \\\"./TableList\\\";\\nimport {Link} from \\\"react-router-dom\\\";\\nimport useFetch from \\\"./useFetch\\\";\\n\\nconst NoteList = () => {\\n  const {notes} = useFetch('note')\\n\\n  return (\\n    <TableList\\n      data={notes}\\n      fieldFormatter={{\\n        title: (title, dataRow) => [\\n          <Link\\n            to={`/edit-note/${dataRow.id}`}\\n            key='1'\\n          >\\n            edit\\n          </Link>,\\n          <span key=\\\"2\\\">\\n            &nbsp;{\\n              title\\n            }\\n          </span>\\n        ],\\n        dateCreated: date => new Date(date).toLocaleString()\\n      }}\\n    />\\n  );\\n};\\n\\nexport default NoteList;\\n\")), mdx(\"p\", null, \"This uses TableList.js to list out Notes.\"), mdx(\"p\", null, \"Finally, change \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"App.js\"), \" to:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-jsx\"\n  }), \"import {\\n  Link,\\n  HashRouter as Router,\\n  Routes,\\n  Route,\\n} from 'react-router-dom';\\nimport AddEditNote from \\\"./AddEditNote\\\";\\nimport NoteList from \\\"./NoteList\\\";\\nimport './App.css';\\n\\nfunction App() {\\n  return (\\n      <div className=\\\"App\\\">\\n        <Router>\\n          <Routes>\\n            <Route exact path=\\\"/\\\" element={\\n              <ul>\\n                <li>\\n                  <Link to=\\\"/note-list\\\">List Notes</Link>\\n                </li>\\n                <li>\\n                  <Link to=\\\"/edit-note\\\">Create Note</Link>\\n                </li>\\n              </ul>\\n            }/>\\n            <Route path=\\\"/note-list\\\" element={<NoteList/>}/>\\n            <Route path=\\\"/edit-note\\\" element={<AddEditNote/>}/>\\n            <Route path=\\\"/edit-note/:noteId\\\" element={<AddEditNote/>}/>\\n          </Routes>\\n        </Router>\\n      </div>\\n  );\\n}\\n\\nexport default App;\\n\")), mdx(\"p\", null, \"If you run this now, you have all basic CRUD operations working.\"), mdx(\"p\", null, \"Congrats, full-stack.\"), mdx(\"p\", null, \"This app is missing a few things: form validation and date handling, also dropdown lists; however, these should be easy things to add...\"), mdx(\"p\", null, \"Code repo: \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://github.com/neohed/node-react-stack\"\n  }), \"Github Repository\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"pageContext":{"id":"5eaeb00a-836c-577b-964a-d2e946ff9468","prev":null,"next":{"id":"eb63bf5d-0384-5318-b1e3-189ac98bb7a8","parent":{"name":"index","sourceInstanceName":"blog"},"excerpt":"Simply Learn Full-Stack React & Node.js Let's jump right in! All the edits we need to make are on the server. We're gonna use Prisma ORM and SqlLite DB for convenience.  We need to install these in  node-server Install the Prisma client which express…","fields":{"title":"simply learn-full-stack-7","description":"Full-stack Adding a Database using Prisma to a Node.js app","slug":"simply-learn-full-stack-7","absolutePath":"D:/Workspace/Github/neohed-blog/content/blog/learn-full-stack-simply-07/index.mdx","banner":null,"date":"2022-11-21T11:23:41"}}}}}